package com.corpgovernment.job.batchimport;

import cn.hutool.core.io.IoUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.corpgovernment.common.base.BaseUserInfo;
import com.corpgovernment.common.common.CorpBusinessException;
import com.corpgovernment.job.batchimport.entity.BatchImportConfig;
import com.corpgovernment.job.batchimport.entity.FillErrorEntity;
import com.corpgovernment.job.batchimport.entity.db.BatchImportErrorLineDo;
import com.corpgovernment.job.batchimport.mapper.BatchImportErrorLineMapper;
import com.corpgovernment.job.batchimport.util.BatchImportExcelUtils;
import com.corpgovernment.job.batchimport.entity.db.BatchImportRecordDo;
import com.corpgovernment.job.batchimport.mapper.BatchImportRecordMapper;
import com.corpgovernment.job.batchimport.enums.BatchImportStatusEnum;
import com.corpgovernment.job.enums.DeletedEnum;
import com.corpgovernment.organization.dataloader.BasicOssClientLoader;
import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.entity.ContentType;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import static com.corpgovernment.organization.constant.OrganizationResponseCodeEnum.BATCH_IMPORT_JOB_FORMAT_ERROR;
import static com.corpgovernment.organization.constant.OrganizationResponseCodeEnum.FILE_IO_ERROR;
import static com.corpgovernment.organization.constant.OrganizationResponseCodeEnum.OSS_ERROR;

/**
 * @author ldma
 * @date 2024/1/8
 */
@Slf4j
public abstract class BatchImportStrategy<T> {

    @Autowired
    private BasicOssClientLoader basicOssClientLoader;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Resource
    private BatchImportRecordMapper batchImportRecordMapper;
    @Autowired
    private BatchImportExcelUtils batchImportExcelUtils;
    @Resource
    private BatchImportErrorLineMapper batchImportErrorLineMapper;

    private static final String ERROR_FILE_PREFIX = "【错误原因导出】";

    public abstract BatchImportConfig<T> batchImportConfigDo();
    public abstract Object preStage(BaseUserInfo baseUserInfo);
    public abstract String validateData(T data, Object object);
    public abstract void saveData(List<T> dataList, Object object);

    public final List<BatchImportRecordDo> pageBatchImportRecordDo(List<String> typeList, String fileName, String uid, Integer pageIndex, Integer pageSize) {
        List<BatchImportRecordDo> batchImportRecordDoList = batchImportRecordMapper.pageBatchImportJobRecordDo(typeList, fileName, uid, pageIndex, pageSize);
        if (batchImportRecordDoList == null) {
            return Collections.emptyList();
        }
        return batchImportRecordDoList;
    }

    public final String downLoadFile(String file) {
        return basicOssClientLoader.privateGeneratePresignedUrl(file);
    }

    public final Integer countBatchImportRecordDo(List<String> typeList) {
        return batchImportRecordMapper.countBatchImportJobRecordDo(typeList);
    }

    public final Integer batchImport(String type, MultipartFile file, BaseUserInfo baseUserInfo) {
        // 保存批量导入记录
        BatchImportRecordDo batchImportRecordDo = new BatchImportRecordDo(type, file.getOriginalFilename(), baseUserInfo);
        // 同步处理
        try(InputStream inputStream = file.getInputStream()) {
            // 拷贝流
            byte[] bytes = IoUtil.readBytes(inputStream);
            // excel格式校验
            validateExcel(IoUtil.toStream(bytes));
            // 获取excel数据
            List<T> dataList = getExcelData(IoUtil.toStream(bytes));
            // 用于校验的前置数据获取
            Object context = preStage(baseUserInfo);
            log.info("【批量导入】校验前置context={}", context);
            // 校验数据
            List<T> validDataList = new ArrayList<>(dataList.size());
            List<FillErrorEntity> fillErrorEntityList = new ArrayList<>(0);
            validateDataList(dataList, context, validDataList, fillErrorEntityList, batchImportRecordDo);
            // 错误记录
            List<BatchImportErrorLineDo> batchImportErrorLineDoList = new ArrayList<>(fillErrorEntityList.size());
            // 设置导入状态
            if (batchImportRecordDo.getErrorCount() != null && batchImportRecordDo.getErrorCount() != 0) {
                batchImportRecordDo.setStatus(BatchImportStatusEnum.FAIL.name());
                // 输出错误文件
                XSSFWorkbook workbook = new XSSFWorkbook(IoUtil.toStream(bytes));
                batchImportExcelUtils.fillError(workbook, fillErrorEntityList);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                workbook.write(byteArrayOutputStream);
                String errorFileUrl = basicOssClientLoader.putPrivateObject(new MockMultipartFile(
                        ERROR_FILE_PREFIX + batchImportRecordDo.getFileName(),
                        ERROR_FILE_PREFIX + batchImportRecordDo.getFileName(),
                        ContentType.APPLICATION_OCTET_STREAM.toString(),
                        new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
                // oss异常
                if (StringUtils.isBlank(errorFileUrl)) {
                    throw new CorpBusinessException(OSS_ERROR);
                }
                batchImportRecordDo.setErrorFileUrl(errorFileUrl);
                // 错误记录
                for (FillErrorEntity fillErrorEntity : fillErrorEntityList) {
                    int lineNum = fillErrorEntity.getRow();
                    String[] errorMsgList = fillErrorEntity.getErrorMsg().split("；");
                    for (String errorMsg : errorMsgList) {
                        batchImportErrorLineDoList.add(new BatchImportErrorLineDo(lineNum, errorMsg));
                    }
                }
            } else {
                batchImportRecordDo.setStatus(BatchImportStatusEnum.SUCCESS.name());
            }
            // 保存数据
            transactionTemplate.execute(status -> {
                saveData(validDataList, context);
                batchImportRecordMapper.insertSelective(batchImportRecordDo);
                if (CollectionUtils.isNotEmpty(batchImportErrorLineDoList)) {
                    batchImportErrorLineDoList.forEach(item -> {
                        item.setRecordId(batchImportRecordDo.getId());
                        item.setDeleted(DeletedEnum.FALSE.getCode());
                        item.setCreateTime(new Date());
                        item.setUpdateTime(new Date());
                    });
                    batchImportErrorLineMapper.batchInsert(batchImportErrorLineDoList);
                }
                return null;
            });
            log.info("【批量导入】批量导入成功 batchImportRecordDo={} fillErrorEntityList={}", batchImportRecordDo, fillErrorEntityList);
            return fillErrorEntityList.size();
        } catch (IOException e) {
            log.error("【批量导入】IO异常", e);
            throw new CorpBusinessException(FILE_IO_ERROR);
        }
    }

    private void validateExcel(InputStream inputStream) throws IOException {
        XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
        XSSFRow row = workbook.getSheetAt(0).getRow(batchImportConfigDo().getHeadRowNumber() - 1);
        List<String> colNameList = batchImportConfigDo().getColNameList();
        for (int i = 0; i < colNameList.size(); i++) {
            if (!colNameList.get(i).equals(row.getCell(i).getStringCellValue())) {
                throw new CorpBusinessException(BATCH_IMPORT_JOB_FORMAT_ERROR);
            }
        }
    }

    private List<T> getExcelData(InputStream inputStream) {
        if (inputStream == null) {
            return null;
        }
        try {
            return EasyExcelFactory.read(inputStream).head(batchImportConfigDo().getClazz()).sheet().headRowNumber(batchImportConfigDo().getHeadRowNumber()).doReadSync();
        } catch (Exception e) {
            log.error("【批量插入】excel解析失败", e);
            return null;
        }
    }

    private void validateDataList(List<T>  dataList, Object context, List<T> validDataList, List<FillErrorEntity> fillErrorEntityList, BatchImportRecordDo batchImportRecordDo) {
        int lineCount = 0;
        int errorCount = 0;
        Integer headRowNumber = batchImportConfigDo().getHeadRowNumber();
        Integer errorCol = batchImportConfigDo().getErrorCol();
        for (T data : dataList) {
            lineCount++;
            String errorMsg;
            try {
                errorMsg = validateData(data, context);
            } catch (Exception e) {
                errorMsg = "校验异常未知错误；";
            }
            if (StringUtils.isBlank(errorMsg)) {
                validDataList.add(data);
            } else {
                errorCount++;
                fillErrorEntityList.add(new FillErrorEntity(headRowNumber+lineCount-1, errorCol, errorMsg));
            }
        }
        batchImportRecordDo.setErrorCount(errorCount);
        batchImportRecordDo.setLineCount(lineCount);
        batchImportRecordDo.setSuccessCount(lineCount-errorCount);
    }

}
